package seedpod.kb2db;

/**
 * Copyright 2009 University of Washington This code is released under the GPL version 3 - with a copy of the license
 * included
 * 
 * @author Maya Hao Li
 */
/**
 * Captures the mapping between KB and DB. This is to capture an instance of the mappings of entities from protege model
 * to relational database entities.
 * 
 * @TODO implement a more generic solution for writing out mapping instances Current version: It writes out SQL
 *       statement to meta tables for a pre-defined RDB schema.
 */

import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;

import seedpod.model.RdbCls;
import seedpod.model.RdbSlot;
import seedpod.model.SeedpodModel;
import seedpod.model.rdb.Attribute;
import seedpod.model.rdb.MetaRdbCls;
import seedpod.model.rdb.MetaRdbSlot;
import seedpod.model.rdb.Rdb;
import seedpod.model.rdb.Relation;
import seedpod.model.rdb.SimpleForeignKey;
import seedpod.model.rdb.View;
import seedpod.rdb.QueryFactory;
import seedpod.rdb.RdbConnection;
import edu.stanford.smi.protege.model.KnowledgeBase;

public class ModelMap {
	private class ModelMapLoader {
		private RdbConnection	_conn;
		private ModelMap		_map;

		public ModelMapLoader(ModelMap map, RdbConnection conn) {
			_map = map;
			_conn = conn;
		}

		/**
		 * Loads the mapping from the database meta tables.
		 * 
		 * @param conn
		 */
		public void importMap() {
			try {
				System.err.println( "loading meta classes... " );
				loadMetaRdbClses();
				loadMetaRdbSlots();
				System.err.println( "... finish loading meta classes " );

				showMap(); // debug print
			}
			catch (SQLException sqle) {
				sqle.printStackTrace();
			}
		}

		public void loadMap( KnowledgeBase kb, Rdb rdb ) {
			Application.runTransform( kb, rdb );
		}

		protected void loadMetaRdbClses() throws SQLException {
			StringBuffer query = QueryFactory.selectQuery( SeedpodModel.RdbCls.RDB_CLASS, null, null );
			ResultSet rs = _conn.executePreparedStatementQuery( query );

			System.err.println( "loading MetaRdbClses: " + query + " executed success? " + (rs != null) );

			while (rs.next()) {
				MetaRdbCls metaCls = MetaRdbCls.create( rs );
				ClsMap cmap = new ClsMap( _map, metaCls );
				_clsMapHash.put( cmap.getRdbClsName(), cmap );
			}
		}

		protected void loadMetaRdbSlots() throws SQLException {
			StringBuffer query = QueryFactory.selectQuery( SeedpodModel.RdbCls.RDB_SLOT, null, null );
			ResultSet rs = _conn.executePreparedStatementQuery( query );

			System.err.println( "loading MetaRdbSlots: " + query + " executed success? " + (rs != null) );

			while (rs.next()) {
				MetaRdbSlot metaSlot = MetaRdbSlot.create( rs );
				SlotMap smap = new SlotMap( _map, metaSlot );
				System.err.println( smap.getDomainCls() + " " + smap.getName() );
				_slotMapHash.put( slotHashKey( smap ), smap );
			}
		}

	}

	static private String	_db;

	// static private String _kb;
	static private ModelMap	_instance;

	public static ModelMap getInstance() {
		if ( _instance == null ) _instance = new ModelMap();
		return _instance;
	}

	private Hashtable<String, ClsMap>	_clsMapHash;

	private Hashtable<String, SlotMap>	_slotMapHash;

	private ModelMap() {
		init();
	}

	public void addMapping( final RdbCls rdbCls, final Relation rel ) {
		ClsMap clsMap = new ClsMap( this, rdbCls, rel );
		_clsMapHash.put( rdbCls.getName(), clsMap );
	}

	/**
	 * maps a rdbCls to a view. consolidate the view mapping to the same cls mapping if it exists sequence
	 * matters:rdbcls is created before its view
	 * 
	 * @param rdbCls
	 * @param view
	 */
	public void addMapping( final RdbCls rdbCls, final View view ) {
		ClsMap clsMap = getClsMap( rdbCls );

		if ( clsMap != null ) clsMap.setView( view );
		else {
			ClsMap viewMap = new ClsMap( this, rdbCls, view );
			_clsMapHash.put( rdbCls.getName(), viewMap );
		}
	}

	public void addMapping( RdbSlot slot, Attribute att ) {
		SlotMap slotMap = new SlotMap( this, slot, att );
		String hashKey = slot != null ? slotHashKey( slot ) : slotHashKey( att );
		_slotMapHash.put( hashKey, slotMap );
	}

	public void addMapping( RdbSlot slot, RdbCls mapToCls ) {
		SlotMap slotMap = getSlotMap( slot );

		if ( slotMap == null ) {
			slotMap = new SlotMap( this, slot, mapToCls );

			_slotMapHash.put( slotHashKey( slot ), slotMap );
		} else {
			ClsMap clsMap = getClsMap( mapToCls );
			if ( clsMap != null ) slotMap.setMapToCls( clsMap );
			else slotMap.setMapToCls( mapToCls.getName() );
		}
	}

	public void addMapping( RdbSlot slot, SimpleForeignKey fk ) {
		SlotMap attMap = getSlotMap( slot );
		if ( attMap != null ) attMap.setForeignKey( fk );
		else {
			SlotMap fkMap = new SlotMap( this, slot, fk );
			_slotMapHash.put( slotHashKey( slot ), fkMap );
		}
	}

	public void addMetaCls( RdbCls rdbCls ) {
		ClsMap clsMap = getClsMap( rdbCls );
		if ( clsMap == null ) {
			ClsMap cMap = new ClsMap( this, rdbCls, null );
			_clsMapHash.put( rdbCls.getName(), cMap );
		}

	}

	private void createClsMetaTbl( PrintWriter out ) {
		MetaRdbCls.serializeSchema( out );
	}

	private void createSlotMetaTbl( PrintWriter out ) {
		MetaRdbSlot.serializeSchema( out );
	}

	public ClsMap getClsMap( RdbCls rdbCls ) {
		return getClsMap( rdbCls.getName() );
	}

	public ClsMap getClsMap( String rdbClsName ) {
		return _clsMapHash.get( rdbClsName );
	}

	public Hashtable<String, ClsMap> getClsMapCollection() {
		return _clsMapHash;
	}

	public Collection<SlotMap> getClsSlotMaps( String rdbClsName ) {
		ClsMap cMap = getClsMap( rdbClsName );
		return cMap.getSlotMaps();
	}

	// get the RdbCls that a slot is mapped to
	// public ClsMap getReifiedClsMap( String rdbSlotName ) {
	// SlotMap slotMap = getSlotMap( rdbSlotName );
	// return (slotMap != null) ? slotMap.getReifiedCls() : null;
	// }

	public String getDb() {
		return _db;
	}

	// key to the slotmaphash is slot[_]domainclass
	public SlotMap getSlotMap( RdbSlot slot ) {
		return _slotMapHash.get( slotHashKey( slot ) );
	}
	

	private void init() {
		_clsMapHash = new Hashtable<String, ClsMap>();
		_slotMapHash = new Hashtable<String, SlotMap>();
	}

	public void loadMap( RdbConnection conn ) {
		ModelMapLoader loader = new ModelMapLoader( this, conn );
		loader.importMap();
	}

	public void serialize( PrintWriter out ) {
		createClsMetaTbl( out );
		createSlotMetaTbl( out );

		serializeClsInstances( out );
		serializeSlotInstances( out );
	}

	protected void serializeClsInstances( PrintWriter out ) {
		Iterator<ClsMap> clsMapIter = _clsMapHash.values().iterator();
		while (clsMapIter.hasNext()) {
			ClsMap cMap = clsMapIter.next();
			cMap.serializeSQL( out );
		}
	}

	protected void serializeSlotInstances( PrintWriter out ) {
		Iterator<SlotMap> slotMapIter = _slotMapHash.values().iterator();
		while (slotMapIter.hasNext()) {
			SlotMap sMap = slotMapIter.next();
			sMap.seralize( out );
		}
	}

	public void setRdb( Rdb _rdb ) {
		_db = _rdb.getName();
	}

	// map print out
	public void showMap() {
		Iterator<String> clsIter = _clsMapHash.keySet().iterator();
		System.err.println( "Number of clses loaded " + _clsMapHash.size() );

		while (clsIter.hasNext()) {
			ClsMap map = _clsMapHash.get( clsIter.next() );
			System.err.println( "++ clsMap: " + map.getName() + "; table:" + map.getTableName() );
		}

		Iterator<String> slotIter = _slotMapHash.keySet().iterator();
		System.err.println( "Number of slots loaded " + _slotMapHash.size() );

		while (slotIter.hasNext()) {
			SlotMap smap = _slotMapHash.get( slotIter.next() );
			System.err.println( "++ slotMap: " + smap.getName() + "; table: " + smap.getDomainCls() );
		}

	}

	private String slotHashKey( Attribute att ) {
		return att.getName() + "_" + att.getRelation().getName();
	}

	private String slotHashKey( RdbSlot slot ) {
		return slot.getName() + "_" + slot.getDomainCls().getName();
	}

	private String slotHashKey( SlotMap slot ) {
		return slot.getName() + "_" + slot.getDomainCls();
	}

	public void updateClsMap( ClsMap clsMap ) {
	// TODO
	}

	public void updateSlotMap( SlotMap slotMpa ) {
	// TODO
	}

}
